package com.whty.cms.filewps.util;

import org.apache.commons.codec.binary.Base64;
import org.apache.commons.lang.ArrayUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.crypto.Cipher;
import java.io.ByteArrayOutputStream;
import java.security.*;
import java.security.spec.KeySpec;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.HashMap;
import java.util.Map;

/**
 * @author xiongxiaofei
 * @date 2015年3月19日
 * @desc RSA非对称加解密以及数字签名工具类
 */
public final class CryptoUtils {

    private static final Logger LOGGER = LoggerFactory.getLogger(CryptoUtils.class);

    // 私钥字符串
    private static final String PRIVATE_KEY = "MIICeAIBADANBgkqhkiG9w0BAQEFAASCAmIwggJeAgEAAoGBALyd5UpTetVGMIj4r1M90WT7kxs457IYC5WwMWiucr9lGiCNFomGp6xPKAnGL9ZrnFa0y9B7Y3VF8pf2XquuhNo87n7WZlvEvEA8XlDqFw75JSATTU8S/wc74hTYOL79zmvjcB7yy1qo46mSmIA2Jvs+nLmFFFgXdPYZbZdIkYjNAgMBAAECgYEAtiZd2kuIIhLoqD5RQrg+UaFKTU3FI45pEaPX81oU4pUGiEz4aNOrdZHz/oxwgkfl8S4kbi/autRUFUEN6ApfrKIdDWI++/9UEQS2IzBOBBlxp8UDiJoghhvv5xVVyyg7LW96UaxAh06iDqQhEoWwCvM+NT6v7k+4jANwd+YZ5hkCQQDeOIQ5vRRLyNaKzqEaAA41rF2mwqb1hp3BjhWVa5+byW/rTdvfHd+TdTN3hus3DjUgzpgnYjyFpPPzLtPeEmgbAkEA2Um3RpfL1/0o2/aKSnNST3Upp48j1T2VGp5H93EVZFcB7cOGxQEKo7+1qHROf9Wu3H3zbcX8/eNYDfEOURAxNwJBANBqvUjR12lEkkNZjKswBiO855Dx21lHykmwkvmzGN0pzjgoq+DUyc4pC/48ofre7mE4ome9sPm/PbWE49zR8qUCQQClkO7vAmFXrdZBbSVIv4CbfOoFfCNQq8D+bFbn66niMA4PLqp1WGrUBRMVTbI25Ap+hhZDsdYpWt7QmLpQ+GaXAkBgByjwK6C6CnrhCnrXhTmclHhZ99RijU/lFAITTAdUobSa1OsKin6QaQXZZE7CRvjPSA99hGoo0fl9sF7l1Ri0";

    // 公钥字符串
    private static final String PUBLIC_KEY = "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQC8neVKU3rVRjCI+K9TPdFk+5MbOOeyGAuVsDFornK/ZRogjRaJhqesTygJxi/Wa5xWtMvQe2N1RfKX9l6rroTaPO5+1mZbxLxAPF5Q6hcO+SUgE01PEv8HO+IU2Di+/c5r43Ae8staqOOpkpiANib7Ppy5hRRYF3T2GW2XSJGIzQIDAQAB";

    // 秘钥算法: RSA非对称加密算法
    private static final String KEY_ALGORITHM = "RSA";

    // 签名算法
    private static final String SIGN_ALGORITHM = "SHA1withRSA";

    // 最大加密块 117字节
    private static final int MAX_ENCRYPT_BLOCK = 117;

    // 最大解密块 128字节
    private static final int MAX_DECRYPT_BLOCK = 128;

    // BASE64编码
    private static Base64 base64 = new Base64();

    // 私钥对象
    private static PrivateKey privateKey = getPrivateKey();

    // 公钥对象
    private static PublicKey publicKey = getPublicKey();

    /**
     * @return
     * @author xiongxiaofei
     * @date 2015年3月19日
     * @desc 私钥加密
     * @param    rawStr
     */
    public static String encryptWithPrivateKey(String rawStr) {
        byte[] data = rawStr.getBytes();
        String encryptStr = null;
        ByteArrayOutputStream out = null;
        try {
            try {
                Cipher cipher = Cipher.getInstance(KEY_ALGORITHM);
                cipher.init(Cipher.ENCRYPT_MODE, privateKey);
                out = new ByteArrayOutputStream();
                for (int i = 0; i < data.length; i += MAX_ENCRYPT_BLOCK) {
                    out.write(cipher.doFinal(ArrayUtils.subarray(data, i, i + MAX_ENCRYPT_BLOCK)));
                }
                encryptStr = base64.encodeToString(out.toByteArray());
            } finally {
                if (out != null) out.close();
            }
        } catch (Exception ex) {
            LOGGER.error(ex.getMessage(), ex);
        }
        return encryptStr;
    }

    /**
     * @return
     * @author xiongxiaofei
     * @date 2015年3月19日
     * @desc 私钥解密
     * @param    encryptStr
     */
    public static String decryptWithPrivateKey(String encryptStr) {
        byte[] data = base64.decode(encryptStr);
        String rawStr = null;
        ByteArrayOutputStream out = null;
        try {
            try {
                Cipher cipher = Cipher.getInstance(KEY_ALGORITHM);
                cipher.init(Cipher.DECRYPT_MODE, privateKey);
                out = new ByteArrayOutputStream();
                for (int i = 0; i < data.length; i += MAX_DECRYPT_BLOCK) {
                    out.write(cipher.doFinal(ArrayUtils.subarray(data, i, i + MAX_DECRYPT_BLOCK)));
                }
                rawStr = new String(out.toByteArray());
            } finally {
                if (out != null) out.close();
            }
        } catch (Exception ex) {
            LOGGER.error(ex.getMessage(), ex);
        }
        return rawStr;
    }

    /**
     * @return
     * @author xiongxiaofei
     * @date 2015年3月19日
     * @desc 私钥生成数字签名
     * @param    key
     */
    public static String signature(String key) {
        String result = null;
        try {
            Signature signature = Signature.getInstance(SIGN_ALGORITHM);
            signature.initSign(privateKey);
            signature.update(key.getBytes());
            byte[] data = signature.sign();
            result = base64.encodeToString(data);
        } catch (Exception ex) {
            LOGGER.error(ex.getMessage(), ex);
        }
        return result;
    }

    /**
     * @return
     * @author xiongxiaofei
     * @date 2015年3月19日
     * @desc 公钥验证数字签名
     * @param    key
     * @param    signatrueStr
     */
    public static boolean verify(String key, String signatrueStr) {
        boolean result = false;
        try {
            Signature signature = Signature.getInstance(SIGN_ALGORITHM);
            signature.initVerify(publicKey);
            signature.update(key.getBytes());
            result = signature.verify(base64.decode(signatrueStr));
        } catch (Exception ex) {
            LOGGER.error(ex.getMessage(), ex);
        }
        return result;
    }

    /**
     * @return
     * @author xiongxiaofei
     * @date 2015年3月19日
     * @desc 公钥加密
     * @param    rawStr
     */
    public static String encryptWithPublicKey(String rawStr) {
        byte[] data = rawStr.getBytes();
        String encryptStr = null;
        ByteArrayOutputStream out = null;
        try {
            try {
                Cipher cipher = Cipher.getInstance(KEY_ALGORITHM);
                cipher.init(Cipher.ENCRYPT_MODE, publicKey);
                out = new ByteArrayOutputStream();
                for (int i = 0; i < data.length; i += MAX_ENCRYPT_BLOCK) {
                    out.write(cipher.doFinal(ArrayUtils.subarray(data, i, i + MAX_ENCRYPT_BLOCK)));
                }
                encryptStr = base64.encodeToString(out.toByteArray());
            } finally {
                if (out != null) out.close();
            }
        } catch (Exception ex) {
            LOGGER.error(ex.getMessage(), ex);
        }
        return encryptStr;
    }

    /**
     * @return
     * @author xiongxiaofei
     * @date 2015年3月19日
     * @desc 公钥解密
     * @param    encryptStr
     */
    public static String decryptWithPublicKey(String encryptStr) {
        byte[] data = base64.decode(encryptStr);
        String rawStr = null;
        ByteArrayOutputStream out = null;
        try {
            try {
                Cipher cipher = Cipher.getInstance(KEY_ALGORITHM);
                cipher.init(Cipher.DECRYPT_MODE, publicKey);
                out = new ByteArrayOutputStream();
                for (int i = 0; i < data.length; i += MAX_DECRYPT_BLOCK) {
                    out.write(cipher.doFinal(ArrayUtils.subarray(data, i, i + MAX_DECRYPT_BLOCK)));
                }
                rawStr = new String(out.toByteArray());
            } finally {
                if (out != null) out.close();
            }
        } catch (Exception ex) {
            LOGGER.error(ex.getMessage(), ex);
        }
        return rawStr;
    }

    /**
     * @return
     * @author xiongxiaofei
     * @date 2015年3月19日
     * @desc 生成私钥对象
     */
    private static PrivateKey getPrivateKey() {
        PrivateKey privateKey = null;
        try {
            KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM);
            KeySpec keySpec = new PKCS8EncodedKeySpec(base64.decode(PRIVATE_KEY));
            privateKey = keyFactory.generatePrivate(keySpec);
        } catch (Exception ex) {
            LOGGER.error(ex.getMessage(), ex);
        }
        return privateKey;
    }

    /**
     * @return
     * @author xiongxiaofei
     * @date 2015年3月19日
     * @desc 生成公钥对象
     */
    private static PublicKey getPublicKey() {
        PublicKey publicKey = null;
        try {
            KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM);
            KeySpec keySpec = new X509EncodedKeySpec(base64.decode(PUBLIC_KEY));
            publicKey = keyFactory.generatePublic(keySpec);
        } catch (Exception ex) {
            LOGGER.error(ex.getMessage(), ex);
        }
        return publicKey;
    }

    /**
     * @author xiongxiaofei
     * @date 2015年3月19日
     * @desc 生成秘钥对
     */
    public static Map<String, String> genKeyPair() {
        Map<String, String> keyMap = new HashMap<>();
        try {
            KeyPairGenerator keyPairGen = KeyPairGenerator.getInstance(KEY_ALGORITHM);
            keyPairGen.initialize(1024);
            KeyPair keyPair = keyPairGen.generateKeyPair();
            PrivateKey privateKey = keyPair.getPrivate();
            PublicKey publicKey = keyPair.getPublic();
            keyMap.put("privateKey", base64.encodeToString(privateKey.getEncoded()));
            keyMap.put("publicKey", base64.encodeToString(publicKey.getEncoded()));
            LOGGER.info("privateKey:\n{}\n", keyMap.get("privateKey"));
            LOGGER.info("publicKey:\n{}", keyMap.get("publicKey"));
        } catch (NoSuchAlgorithmException ex) {
            LOGGER.error(ex.getMessage(), ex);
        }
        return keyMap;
    }

    public static void main(String[] args) {
        String text = "Base64编码要求把3个8位字节（3*8=24）转化为4个6位的字节（4*6=24），之后在6位的前面补两个0，形成8位一个字节的形式。 如果剩下的字符不足3个字节，则用0填充，输出字符使用'='，因此编码后输出的文本末尾可能会出现1或2个'='。";

        // 私钥加密，公钥解密
        String encryptStr = encryptWithPrivateKey(text);
        System.out.println(encryptStr);
        String rawStr = decryptWithPublicKey(encryptStr);
        System.out.println(rawStr);

        System.out.println();

        // 公钥加密，私钥解密
        encryptStr = encryptWithPublicKey(text);
        System.out.println(encryptStr);
        rawStr = decryptWithPrivateKey(encryptStr);
        System.out.println(rawStr);

        System.out.println();

        // 私钥签名，公钥验证
        String key = "timestamp=" + System.currentTimeMillis() + "&userName=xiongxiaofei";
        String signatrueStr = signature(key);
        System.out.println(signatrueStr);
        System.out.println(verify(key, signatrueStr));
    }

}
